package com.webchat.ugc.service.moment;

import com.webchat.common.bean.APIResponseBean;
import com.webchat.common.constants.ResourceBehaviorConstants;
import com.webchat.common.constants.WebConstant;
import com.webchat.common.enums.AccountRelationTypeEnum;
import com.webchat.common.enums.RedisKeyEnum;
import com.webchat.common.service.RedisService;
import com.webchat.common.util.DateUtils;
import com.webchat.domain.vo.response.UserBaseResponseInfoVO;
import com.webchat.domain.vo.response.moment.MomentDetailVO;
import com.webchat.domain.vo.response.moment.MomentVO;
import com.webchat.rmi.act.IResourceBehaviorClient;
import com.webchat.ugc.repository.dao.IMomentTimeLineDAO;
import com.webchat.ugc.repository.entity.MomentTimeLineEntity;
import com.webchat.ugc.service.AccountService;
import com.webchat.ugc.service.ResourceBehaviorService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;


@Service
public class MomentTimeLineService {

    @Autowired
    private IMomentTimeLineDAO momentTimeLineDAO;
    @Autowired
    private RedisService redisService;
    @Autowired
    private AccountService accountService;
    @Autowired
    private MomentService momentService;
    @Autowired
    private ResourceBehaviorService resourceBehaviorService;

    private static final int MAX_LIKE_USER_SIZE = 10;

    /**
     * TODO  数据一致性保证
     *
     * @param author
     * @param momentId
     * @param time
     */
    public void addTimeLine(String author, Long momentId, Long time, boolean writeFriends) {
        Set<String> friends = new HashSet<>();
        if (writeFriends) {
            friends = accountService.getAllSubscriberByAccount(author, AccountRelationTypeEnum.USER_USER);
        }
        friends.add(author);
        if (CollectionUtils.isEmpty(friends)) {
            return;
        }

        // TODO BY策略过滤不需要写入的用户

        /**
         * 写扩散：双写 MySQL + 冷备
         */
        Date publishDate = new Date(time);
        // 持计划话朋友圈数据
        List<MomentTimeLineEntity> momentTimeLineEntities = friends.stream().map(u -> {
            MomentTimeLineEntity momentTimeLineEntity = new MomentTimeLineEntity();
            momentTimeLineEntity.setUserId(u);
            momentTimeLineEntity.setMomentId(momentId);
            momentTimeLineEntity.setTimeLine(publishDate);
            return momentTimeLineEntity;
        }).collect(Collectors.toList());
        this.saveMomentTimeLine(momentTimeLineEntities);

        // TODO 这里需要写冷备

        // 写时间线缓存
        this.saveMomentTimeLineCache(friends, momentId);
    }


    /**
     * 朋友圈数据加载
     *
     * @param userId
     * @param lastMomentId
     * @param size
     * @return
     */
    public List<MomentDetailVO> timeline(String userId, Long lastMomentId, int size) {

        String timeLineKey = this.userTimelineCacheKey(userId);
        String noneCacheKey = this.userTimelineNoneHotCacheKey(userId);
        // 为什么这里需要noneCacheKey，原因为保证短时间只查询一次库
        // 无热点缓存存在说明：我们MySQL无3天内热点数据，但是可能存在3天外的冷备数据
        if (!redisService.exists(noneCacheKey) && !redisService.exists(timeLineKey)) {
            // BY用户时间线查询，不存在并发场景，所以这里不需要加锁
            this.refreshUserTimelineCache(userId);
        }
        Set<String> caches = redisService.zreverseRangeByScore(timeLineKey, lastMomentId - 1, 0, size);
//        if (caches.size() < size) {
            // 说明redis缓存的数据不够显示
            // 1、本身用户朋友圈数据就是很少
            // 2、用户加载到中间页，redis3天内数据不够显示
            // TODO 当前页数据不足{size}条，重新查询冷备（MongoDB）
//            caches = this.queryMomentsByMongoDB(userId, lastMomentId, size);
//        }
        if (CollectionUtils.isEmpty(caches)) {
            // mysql、redis、冷备都同时没查询到数据，说明数据已经加载完
            return Collections.emptyList();
        }
        List<Long> momentIds = caches.stream().map(Long::parseLong).collect(Collectors.toList());
        List<MomentVO> momentVOList = momentService.batchGetMomentFromCache(momentIds);
        Set<String> authorIds = momentVOList.stream().map(MomentVO::getAuthor).collect(Collectors.toSet());
        Map<String, UserBaseResponseInfoVO> authorInfoMap = accountService.batchGet(authorIds);
        // 批量获取动态点赞量
        Map<Long, Long> momentLikeCountMap = resourceBehaviorService.countBehavior(
                                                ResourceBehaviorConstants.BehaviorType.LIKE.name(),
                                                ResourceBehaviorConstants.ResourceType.MOMENT.name(),
                                                momentIds);
        // 获取当前登录用户点赞状态
        Map<Long, Boolean> userMomentLikeStatusMap = resourceBehaviorService.isBehavior(
                userId,
                ResourceBehaviorConstants.BehaviorType.LIKE.name(),
                ResourceBehaviorConstants.ResourceType.MOMENT.name(),
                momentIds);
        Map<String, Set<String>> momentLikeUserMap = resourceBehaviorService.listBehaviorUsers(ResourceBehaviorConstants.BehaviorType.LIKE.name(),
                ResourceBehaviorConstants.ResourceType.MOMENT.name(),
                momentIds,
                MAX_LIKE_USER_SIZE);

        Set<String> queryUserIds = new HashSet<>();
        for (Set<String> userIds : momentLikeUserMap.values()) {
            if (CollectionUtils.isEmpty(userIds)) {
                queryUserIds.addAll(userIds);
            }
        }
        // 查询当前用户跟互动用户之间是否共同好友
        Map<String, Boolean> subscribeMap = accountService.isBatchSubscribe(
                                            userId, new ArrayList<>(queryUserIds), AccountRelationTypeEnum.USER_USER);
        // 查询互动人员基础信息
        Map<String, UserBaseResponseInfoVO> userMap = accountService.batchGet(queryUserIds);

//        Map<Long, Long> momentCommentCountMap = resourceBehaviorService.countBehavior(
//                ResourceBehaviorConstants.BehaviorType.LIKE.name(),
//                ResourceBehaviorConstants.ResourceType.MOMENT.name(),
//                momentIds);

        return momentVOList.stream().map(m -> {
            MomentDetailVO momentDetail = new MomentDetailVO();
            BeanUtils.copyProperties(m, momentDetail);
            momentDetail.setAuthorInfo(authorInfoMap.get(m.getAuthor()));
            momentDetail.setLikeCount(momentLikeCountMap.getOrDefault(m.getId(), 0L));
            momentDetail.setIsLike(userMomentLikeStatusMap.getOrDefault(m.getId(), false));
            momentDetail.setCommentCount(0L);
            // 设置每条动态点赞过的人员列表
            Set<String> likeUserIds = momentLikeUserMap.get(m.getId().toString());
            List<UserBaseResponseInfoVO> likeUsers = Collections.emptyList();
            if (CollectionUtils.isNotEmpty(likeUserIds)) {
                likeUsers = likeUserIds.stream()
                        .map(uid -> userMap.get(uid))
                        // 保留真实存在的用户
                        .filter(u -> u != null)
                        // 保留共同好友
                        .filter(u -> subscribeMap.getOrDefault(u.getUserId(), false))
                        .toList();
            }
            momentDetail.setLikeUsers(likeUsers);
            return momentDetail;
        }).collect(Collectors.toList());
    }

    private Set<String> queryMomentsByMongoDB(String userId, Long lastMomentId, int size) {
        // TODO
        return null;
    }

    private String userTimelineCacheKey(String userId) {

        return RedisKeyEnum.MOMENT_TIME_LINE_CACHE.getKey(userId);
    }

    private String userTimelineNoneHotCacheKey(String userId) {

        return RedisKeyEnum.MOMENT_TIME_LINE_NONE_HOT_DATE_CACHE.getKey(userId);
    }

    /**
     * 删除用户无热点数据标识
     *
     */
    private void delUserTimelineNoneHotCache(Set<String> userIds) {

        List<String> noneCacheKeys = userIds.stream().map(this::userTimelineNoneHotCacheKey).collect(Collectors.toList());
        redisService.removeAll(noneCacheKeys);
    }

    /**
     * 刷新用户朋友圈时间线缓存
     *
     * @param userId
     */
    private void refreshUserTimelineCache(String userId) {

        // 计算3天前时间
        Date afterDate = DateUtils.getBeforeNDayDate(3);
        List<MomentTimeLineEntity> momentTimeLineEntities = momentTimeLineDAO.findAllByUserIdAndTimeLineAfter(userId, afterDate);
        if (CollectionUtils.isNotEmpty(momentTimeLineEntities)) {
            // 刷新时间线redis
            Set<ZSetOperations.TypedTuple<String>> typedTupleSet = new HashSet<>();
            for (MomentTimeLineEntity momentTimeLine : momentTimeLineEntities) {
                String value = momentTimeLine.getMomentId().toString();
                double score = momentTimeLine.getMomentId();
                ZSetOperations.TypedTuple<String> tuple = new DefaultTypedTuple<>(value, score);
                typedTupleSet.add(tuple);
            }
            String timeLineKey = this.userTimelineCacheKey(userId);
            redisService.zadd(timeLineKey, typedTupleSet, RedisKeyEnum.MOMENT_TIME_LINE_CACHE.getExpireTime());
        } else {
            String noneHotKey = RedisKeyEnum.MOMENT_TIME_LINE_NONE_HOT_DATE_CACHE.getKey(userId);
            redisService.set(noneHotKey, WebConstant.CACHE_NONE, RedisKeyEnum.MOMENT_TIME_LINE_NONE_HOT_DATE_CACHE.getExpireTime());
        }
    }

    /**
     * 写入用户朋友圈时间线缓存
     * 注意：这里redis sorted set中的score我们没有使用动态发布时间戳/写入时间线的时间戳，
     * 主要是避免并发场景下score相同导致用户端分页查询可能出现重复数据、丢数据问题。所以我们使用唯一动态id作为，同时保证顺序性。
     *
     * @param friends
     * @param momentId
     */
    private void saveMomentTimeLineCache(Set<String> friends, Long momentId) {

        List<String> keys = friends.stream().map( u ->
                RedisKeyEnum.MOMENT_TIME_LINE_CACHE.getKey(u)).collect(Collectors.toList());
        keys.forEach(key -> {
            if (!redisService.exists(key)) {
                // 主动刷新用户朋友圈时间线缓存（3天热点数据）
//                this.refreshUserTimelineCache(key);
            }
            redisService.zadd(key, String.valueOf(momentId), momentId,
                    RedisKeyEnum.MOMENT_TIME_LINE_CACHE.getExpireTime());
        });
        // 删除用户无热点数据缓存
        this.delUserTimelineNoneHotCache(friends);
    }

    private void saveMomentTimeLine(List<MomentTimeLineEntity> momentTimeLineEntities) {
        if (CollectionUtils.isEmpty(momentTimeLineEntities)) {
            return;
        }
        momentTimeLineDAO.saveAll(momentTimeLineEntities);
    }


    public void save(String userId) {
        MomentTimeLineEntity entity = new MomentTimeLineEntity();

        entity.setUserId(userId);
        entity.setMomentId(1L);
        entity.setTimeLine(new Date());
        momentTimeLineDAO.save(entity);
    }


//    public static void main(String[] args) {
//        String userId = "U_1bb8a10f2d2a45a4b075c20016f10cb8";
//        int[] idxArr = new int[3];
//        for (int i = 0; i < 1000; i++) {
//
//            userId += i;
//            int idx = Math.abs(userId.hashCode()) % 3;
//            idxArr[idx] = idxArr[idx] + 1;
//        }
//        System.out.println("0:" + idxArr[0]);
//        System.out.println("1:" + idxArr[1]);
//        System.out.println("2:" + idxArr[2]);
//    }

}
